// for Linux/Mac -cp "./build/classes:./lib:./lib/Base64.jar:./lib/iiop.jar:./lib/jade.jar:./lib/jadeTools.jar:./lib/commons-codec-1.3.jar" jade.Boot -nomtp central:sma.CentralAgent;coord:sma.CoordinatorAgent;HC:sma.HarvesterCoordinatorAgent;SC:sma.ScoutCoordinatorAgent


package sma;

import java.util.ArrayList;
import java.util.Arrays;
import java.lang.*;
import java.io.*;
import java.sql.SQLException;
import java.sql.ResultSet;
//import jade.util.leap.ArrayList;
import jade.util.leap.List;
import jade.core.*;
import jade.core.behaviours.*;
import jade.domain.*;
import jade.domain.FIPAAgentManagement.*;
import jade.domain.FIPANames.InteractionProtocol;
import jade.lang.acl.*;
import jade.content.*;
import jade.content.onto.*;
import jade.proto.AchieveREInitiator;
import jade.proto.AchieveREResponder;

import sma.ontology.*;
import sma.gui.*;
import java.util.*;

/**
 * <p><B>Title:</b> IA2-SMA</p>
 * <p><b>Description:</b> Practical exercise 2013-14. Recycle swarm.</p>
 * <p><b>Copyright:</b> Copyright (c) 2011</p>
 * <p><b>Company:</b> Universitat Rovira i Virgili (<a
 * href="http://www.urv.cat">URV</a>)</p>
 * @author not attributable
 * @version 2.0
 */
public class CentralAgent extends Agent {
  
  private AuxInfo info;
  private sma.gui.GraphicInterface gui;
  private sma.ontology.InfoGame game;
  private AID coordinatorAgent;
  private sma.ontology.InfoGame publicGame;

  private jade.wrapper.AgentContainer ac;
  
  public CentralAgent() {
    super();
  }

  /**
   * A message is shown in the log area of the GUI
   * @param str String to show
   */
  private void showMessage(String str) {
    if (gui!=null) gui.showLog(str + "\n");
    System.out.println(getLocalName() + ": " + str);
  }

  
  
   
    
    
  private java.util.List<Cell> placeAgents(InfoGame currentGame) throws Exception {
      Random random= new Random();
      java.util.List<Cell> agents = new java.util.ArrayList<Cell>();
      
      // Declaring the array that will contain the taken positions of the scouts and harvesters
      // in order to know where to place them, so that they do not overlap
    
       ArrayList<Integer> takenPositionsX =new ArrayList<Integer>();
       ArrayList<Integer> takenPositionsY =new ArrayList<Integer>();
      
      for(int k=0; k<currentGame.getInfo().getNumScouts(); k++) {
        InfoAgent b = new InfoAgent(InfoAgent.SCOUT);
      
         // To get number of rows and columns of the map
         int NROWS = game.getRowsMap();
         int NCOLS = game.getColsMap();
         int x=0;
         int y=0;
         
        int num=0;
        // Num is the type of cell: 1-building 2-street 3-recycling center
        while(num!=2){
        // until we don't find a street cell 
            x=  random.nextInt(NROWS);
            y=  random.nextInt(NCOLS);  
         if((takenPositionsX.isEmpty()&&takenPositionsY.isEmpty())){
           // for the first iteration the takenPositions are emtpy so we generate
           // the first random positions  
           x=  random.nextInt(NROWS);
           y=  random.nextInt(NCOLS);
         }
         else{  
         while(takenPositionsX.contains(x)&&takenPositionsY.contains(y)){   
        // Generate random integer to pick a random cell
           x=  random.nextInt(NROWS);
           y=  random.nextInt(NCOLS); //generates a random number between 0 and NROWS-1
        
         }
        }
        // Get the cell type, to make sure we place scouts in a street 
         num=game.wantType(x, y);
        }
       
        //Add the positions of the scouts to an arrayList
        takenPositionsX.add(x);
        takenPositionsY.add(y);
                
        ((currentGame.getInfo().getMap())[x][y]).addAgent(b);
        agents.add(currentGame.getInfo().getCell(x,y));
      }
      
      for(int k=0; k<currentGame.getInfo().getNumHarvesters(); k++) {
    	InfoAgent p = new InfoAgent(InfoAgent.HARVESTER);
         // Get number of rows and columns of the map
         int NROWS = game.getRowsMap();
         int NCOLS = game.getColsMap();
       
        int x=0;
        int y=0;
        int num=0;
        while(num!=2){
           x=  random.nextInt(NROWS);
           y=  random.nextInt(NCOLS);
      
         while(takenPositionsX.contains(x)&&takenPositionsY.contains(y)){   
        // Generate random integer to pick a random cell, this time the arrayList
        // is not empty, so we do not need the initial if, as in the scouts case     
          x=  random.nextInt(NROWS);
          y=  random.nextInt(NCOLS);
            }
        // Get the cell type, to make sure we place harvesters in a street 
         num=game.wantType(x, y);
        }
        //Add the positions of the scouts to an arrayList
        takenPositionsX.add(x);
        takenPositionsY.add(y);
        
      
        ((currentGame.getInfo().getMap())[x][y]).addAgent(p);
        agents.add( currentGame.getInfo().getCell(x,y));
      }
      return agents;
    }
  
    
  /**
   * Agent setup method - called when it first come on-line. Configuration
   * of language to use, ontology and initialization of behaviours.
   */
  protected void setup() {

    /**** Very Important Line (VIL) *********/
    this.setEnabledO2ACommunication(true, 1);
    /****************************************/

    // showMessage("Agent (" + getLocalName() + ") .... [OK]");

    // Register the agent to the DF
    ServiceDescription sd1 = new ServiceDescription();
    sd1.setType(UtilsAgents.CENTRAL_AGENT);
    sd1.setName(getLocalName());
    sd1.setOwnership(UtilsAgents.OWNER);
    DFAgentDescription dfd = new DFAgentDescription();
    dfd.addServices(sd1);
    dfd.setName(getAID());
    try {
      DFService.register(this, dfd);
      showMessage("Registered to the DF");
    }
    catch (FIPAException e) {
      System.err.println(getLocalName() + " registration with DF unsucceeded. Reason: " + e.getMessage());
      doDelete();
    }
    

   /**************************************************/

    try {
      this.game = new InfoGame(); //object with the game data
      this.game.readGameFile("game.txt");
      this.publicGame = new InfoGame(); // game data to publish
      this.publicGame.readGameFile("game.txt");
      //game.writeGameResult("result.txt", game.getMap());
    } catch(Exception e) {
      e.printStackTrace();
      System.err.println("Game NOT loaded ... [KO]");
    }
    try {
      this.gui = new GraphicInterface(game);
      gui.setVisible(true);
      showMessage("Game loaded ... [OK]");
    } catch (Exception e) {
      e.printStackTrace();
    }
    
    showMessage("Buildings with garbage");
    for (Cell c : game.getBuildingsGarbage()) showMessage(c.toString());
    
    
   /****Agents are randomly placed****************/
   java.util.List<Cell> agents = null;
   try{
	   agents = placeAgents(this.game);
   }catch(Exception e){}
   
   /**************************************************/
   this.game.getInfo().fillAgentsInitialPositions(agents);

   //If any scout is near a building with garbage, we show it in the public map
   java.util.List<Cell> remove = new LinkedList<Cell>();
   
   for (Cell a : agents){
	    if (a.getAgent().getAgentType()==InfoAgent.SCOUT){
	    	for (Cell b : game.getBuildingsGarbage()){
	    		int x=a.getRow(); int y=a.getColumn();
	    		int xb=b.getRow(); int yb=b.getColumn();
	    		if (x>0){
	    			if ((xb==x-1) && (yb==y)) {game.getInfo().setCell(xb, yb, b); remove.add(b);}
		    		if ((y>0) && (xb==x-1) && (yb==y-1)) {game.getInfo().setCell(xb, yb, b); remove.add(b);} 
		    		if ((y<game.getInfo().getMap()[x].length-1) && (xb==x-1) && (yb==y+1)) {game.getInfo().setCell(xb, yb, b); remove.add(b);}
		    	}
		    	if (x<game.getInfo().getMap().length-1){
		    		if ((xb==x+1) && (yb==y)) {game.getInfo().setCell(xb, yb, b); remove.add(b);}
		    		if ((y>0) && (xb==x+1) && (yb==y-1)) {game.getInfo().setCell(xb, yb, b); remove.add(b);}
		    		if ((y<game.getInfo().getMap()[x].length-1) && (xb==x+1) && (yb==y+1)) {game.getInfo().setCell(xb, yb, b); remove.add(b);}
		    	}
		    	if ((y>0) && (xb==x) && (yb==y-1)) {game.getInfo().setCell(xb, yb, b); remove.add(b);}
				if ((y<game.getInfo().getMap()[x].length-1) && (xb==x) && (yb==y+1)) {game.getInfo().setCell(xb, yb, b); remove.add(b);}
	    	}
	    }	   
   }
   for (Cell r : remove)  game.getBuildingsGarbage().remove(r);
        
   // search CoordinatorAgent
   ServiceDescription searchCriterion = new ServiceDescription();
   searchCriterion.setType(UtilsAgents.COORDINATOR_AGENT);
   this.coordinatorAgent = UtilsAgents.searchAgent(this, searchCriterion);
   // searchAgent is a blocking method, so we will obtain always a correct AID

   
   // add behaviours

   // we wait for the initialization of the game
  //  MessageTemplate mt = MessageTemplate.and(MessageTemplate.MatchProtocol(InteractionProtocol.FIPA_REQUEST), MessageTemplate.MatchPerformative(ACLMessage.REQUEST));
  //  this.addBehaviour(new RequestResponseBehaviour(this, null));
   
   // Setup finished. When the last inform is received, the agent itself will add
   // a behavious to send/receive actions

     this.addBehaviour(new TurnControlBehavior(this, game.getTimeout()));
     
  } //endof setup

  
  /*************************************************************************/

  /**
   * <p><B>Title:</b> IA2-SMA</p>
   * <p><b>Description:</b> Practical exercise 2011-12. Recycle swarm.</p>
   * Class that receives the REQUESTs from any agent. Concretely it is used 
   * at the beginning of the game. The Coordinator Agent sends a REQUEST for all
   * the information of the game and the CentralAgent sends an AGREE and then
   * it sends all the required information.
   * <p><b>Copyright:</b> Copyright (c) 2009</p>
   * <p><b>Company:</b> Universitat Rovira i Virgili (<a
   * href="http://www.urv.cat">URV</a>)</p>
   * @author David Isern and Joan Albert López
   * @see sma.ontology.Cell
   * @see sma.ontology.InfoGame
   */
  private class RequestResponseBehaviour extends AchieveREResponder {

    /**
     * Constructor for the <code>RequestResponseBehaviour</code> class.
     * @param myAgent The agent owning this behaviour
     * @param mt Template to receive future responses in this conversation
     */
    public RequestResponseBehaviour(CentralAgent myAgent, MessageTemplate mt) {
      super(myAgent, mt);
      showMessage("Waiting REQUESTs from authorized agents");
    }

    protected ACLMessage prepareResponse(ACLMessage msg) {
      /* method called when the message has been received. If the message to send
       * is an AGREE the behaviour will continue with the method prepareResultNotification. */
      ACLMessage reply = msg.createReply();
      try {
        Object contentRebut = (Object)msg.getContent();
        if(contentRebut.equals("Initial request")) {
          showMessage("Initial request received");
          reply.setPerformative(ACLMessage.AGREE);
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
      showMessage("Answer sent"); //: \n"+reply.toString());
      return reply;
    } //endof prepareResponse   

    /**
     * This method is called after the response has been sent and only when
     * one of the following two cases arise: the response was an agree message
     * OR no response message was sent. This default implementation return null
     * which has the effect of sending no result notification. Programmers
     * should override the method in case they need to react to this event.
     * @param msg ACLMessage the received message
     * @param response ACLMessage the previously sent response message
     * @return ACLMessage to be sent as a result notification (i.e. one of
     * inform, failure).
     */
    protected ACLMessage prepareResultNotification(ACLMessage msg, ACLMessage response) {

      // it is important to make the createReply in order to keep the same context of
      // the conversation
      ACLMessage reply = msg.createReply();
      reply.setPerformative(ACLMessage.INFORM);

      try {
        reply.setContentObject(game.getInfo());
      } catch (Exception e) {
        reply.setPerformative(ACLMessage.FAILURE);
        System.err.println(e.toString());
        e.printStackTrace();
      }
      showMessage("Answer sent"); //+reply.toString());
      return reply;

    } //endof prepareResultNotification


    /**
     *  No need for any specific action to reset this behaviour
     */
    public void reset() {
    }

  } //end of RequestResponseBehaviour

   private class TurnControlBehavior extends TickerBehaviour{

	/**
	 * 
	 */
	private static final long serialVersionUID = -4793117331763237218L;
        
	public TurnControlBehavior(Agent arg0, long arg1) {
		super(arg0, arg1);
		showMessage("Turn Control Initiated timeout is "+game.getTimeout());
	}

	@Override
	protected void onTick() {
		game.incrTurn();
		showMessage(" ********** Turn "+game.getTurn()+ " **********");
		if ((game.getTurn()==game.getGameDuration()))//||(stats.isFinished()))
		{
			showMessage("Game Finished");
			this.stop();
			try {
				game.writeGameResult("result.txt", game.getInfo().getMap());
			} catch (IOException e) {
				showMessage("Cannot write the game results");
				e.printStackTrace();
			} catch (Exception e) {
				showMessage("General error writing game results");
				e.printStackTrace();
			}
	//		stats.show();
		}else{
			//Send updated map to all Coordinator Agent
		    ACLMessage requestInicial = new ACLMessage(ACLMessage.REQUEST);
		    requestInicial.clearAllReceiver();
		    requestInicial.addReceiver(coordinatorAgent);
		    requestInicial.setProtocol(InteractionProtocol.FIPA_QUERY);
		    try {
		      requestInicial.setContentObject(game.getInfo());
		    } catch (Exception e) {
                        System.out.println("No puedo inicializar el mensaje!! :-S");
		      e.printStackTrace();
		    }
		    //we add a behavior that sends the message and waits for an answer
		   
                    this.myAgent.addBehaviour(new Informer(this.myAgent, requestInicial));
                    updatePublicGame();
                    
                }
	} 
  }
  /*************************************************************************/
   
 private void updatePublicGame(){
	publicGame.setInfo(game.getInfo());
	for(int r=0;r<game.getInfo().getMap().length;r++)
		for(int c=0;c<game.getInfo().getMap()[r].length;c++)
		{
			//if (game.getInfo().getCell(r, c).isDiscovered())
			//{
			//	publicGame.getInfo().setCell(r, c, game.getInfo().getCell(r, c));
			//}else
			//	publicGame.getInfo().setCell(r, c, null);
                    publicGame.getInfo().setCell(r, c, game.getInfo().getCell(r, c));
		}
}
 
 class Informer extends AchieveREInitiator
  {

	@Override
	protected void handleAgree(ACLMessage arg0) {
		super.handleAgree(arg0);
	}

	@Override
	protected void handleInform(ACLMessage arg0) {
		// Auto-generated method stub
		//super.handleInform(arg0);
	}

	

	public Informer(Agent arg0, ACLMessage arg1) {
		super(arg0, arg1);
	}
	  
	
  }
  
protected void createAgents() {
    
    updatePublicGame();
    AuxInfo info = game.getInfo();
    UtilsAgents util = new UtilsAgents();
    jade.wrapper.AgentContainer agentContainer = null;
                
    HashMap<?, ?> hm = info.getAgentsInitialPosition();
     
    int agentNumber = 1;
    for (Object infoAgentAux : hm.keySet()) {
        InfoAgent ia = (InfoAgent)infoAgentAux;
        AID aidAux;
        try{
           String name = ia.getAgent()+agentNumber;
           int agentType = ia.getAgentType();
           
           if(agentNumber == 1){
               switch(agentType){
                    case 0: agentContainer = util.createAgent(name, "sma.ScoutAgent", null); break;
                    case 1: agentContainer = util.createAgent(name, "sma.HarvesterAgent", null); break;
               }
           }else{
                switch(agentType){
                    case 0: util.createAgent(agentContainer, name, "sma.ScoutAgent", null); break;
                    case 1: util.createAgent(agentContainer, name, "sma.HarvesterAgent", null); break;
                }
                ServiceDescription searchCriterion = new ServiceDescription();
                searchCriterion.setName(name);
                aidAux = UtilsAgents.searchAgent(this, searchCriterion);
                ia.setAID(aidAux);
           }
        }catch(Exception e){
            System.out.println("Excepcion: Couldn't create an Agent.");
        }
        agentNumber++;    
    }
          
    }

} //endof class AgentCentral
